package AbstractMapper;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;

import org.opencyc.cycobject.CycList;

//represents an analogy built from an <ArrayList<AbstractElementPair>> with a bunch of double scores
public class AbstractAnalogy implements Comparable<AbstractAnalogy>{
	private CycConnection cycConn;
	private Double firstDistanceSums, inverseDistanceSums, totalMatchSums, zScoreSums, angleSums;
	private Double firstDistanceAvg, inverseDistanceAvg, totalMatchAvg, zScoreAvg, angleAvg;
	private ArrayList<AbstractElementPair> maps = null;
	private ArrayList<String> mapsByName = null;
	
	protected String myVoteTally; //TODO fix this temporary var 
	
	public AbstractAnalogy(CycConnection cycC)	
	{
		this.cycConn = cycC;		
		maps = new ArrayList<AbstractElementPair>();
		this.init();
	}
	public AbstractAnalogy(CycConnection cycC, ArrayList<AbstractElementPair> mappings)	
	{
		this.cycConn = cycC;		
		maps = new ArrayList<AbstractElementPair>(mappings);
		this.init();
	}	
	public AbstractAnalogy(ArrayList<String> oneAnalogyNames, CycConnection cycBridge) {
		this.mapsByName = new ArrayList<String>(oneAnalogyNames);
		this.cycConn = cycBridge;
		this.init();
	}
	private void init() //clears the scores but leaves the mappings and the cycConnection
	{		
		firstDistanceSums = new Double(0);
		inverseDistanceSums = new Double(0);
		totalMatchSums = new Double(0);
		zScoreSums = new Double(0);
		firstDistanceAvg = new Double(0);
		inverseDistanceAvg = new Double(0);
		totalMatchAvg = new Double(0);
		zScoreAvg = new Double(0);
		angleSums = new Double(0);
		angleAvg = new Double(0);
		myVoteTally = "";
	}
	protected ArrayList<AbstractElementPair> getMaps()
	{
		return this.maps;
	}
	
	protected String setVoteTally(String votes)
	{		
		this.myVoteTally = votes.toString();
		return this.myVoteTally;		
	}

	@Override
	public String toString()
	{
		return "\nMapping(s): " + maps + "\n" +
		"totalHit scores (first sum, then avg):        " + Arrays.toString(this.getTotalMatches()) +	"\n" +
		"inverseDistance scores (first sum, then avg): " + Arrays.toString(this.getInverseDistances()) + "\n" +
		"firstDistances scores (first sum, then avg):  " + Arrays.toString(this.getFirstDistances()) + "\n" +
		"zScores (first sum, then avg):                " + Arrays.toString(this.getzScores()) + "\n" +
		"Angles (first sum, then avg):                 " +  Arrays.toString(this.getVectorAngles()) + "\n" +
		"votes:                                        " + this.myVoteTally + "\n";
	}	
	public Double[] getTotalMatches()
	{		
		Double[] totalMatches = new Double[]{this.totalMatchSums, this.totalMatchAvg};
		return (totalMatches);
	}
	public Double[] getInverseDistances()
	{
		Double[] totalMatches = new Double[]{this.inverseDistanceSums, this.inverseDistanceAvg}; 
		return (totalMatches);
	}
	public Double[] getFirstDistances()
	{
		Double[] totalMatches = new Double[]{this.firstDistanceSums, this.firstDistanceAvg}; 
		return (totalMatches);
	}
	public Double[] getzScores()
	{
		Double[] totalMatches = new Double[]{this.zScoreSums, this.zScoreAvg}; 
		return (totalMatches);
	}
	public Double[] getVectorAngles() {
		Double[] totalAngles = new Double[]{this.angleSums, this.angleAvg}; 
		return (totalAngles);
	}
	
	public ArrayList<CycList> getAllFirstMatches()//TODO FIGURE OUT HOW TO USE OR DELETE
	{
		ArrayList<CycList> concatenation = new ArrayList<CycList>();
		if (!maps.isEmpty())
		{
			for (int i = 0; i < maps.size(); i++)			
				concatenation.add(maps.get(i).getFirstMatches());			
		}
		return concatenation;
	}	

	public void populate(ArrayList<AbstractElementPair> mappings)
	{
		maps = new ArrayList<AbstractElementPair>(mappings);
	}
	public boolean populate()//populate self from a list of pair names and a hashtable of all possible pairs
	{
		maps = new ArrayList<AbstractElementPair>();
		AbstractElementPair el;
		for (int i= 0; i < mapsByName.size(); i++)
		{
			el = this.cycConn.allPairs.get(mapsByName.get(i));			
			if (el == null)			
				return false;			
			else			
				maps.add(el);
		}
		return true;
	}
	
	public int compareTo(AbstractAnalogy res) 
	{		
		Double[] avgNsum = this.getSelectedScore(); //get sum and avg of the score that is being used for sorting
		
		if (avgNsum[1] > res.getSelectedScore()[1])
			return 1;
		else if (avgNsum[1] < res.getSelectedScore()[1])
			return -1;
        else // (_score == res.getScore()) If avgs scores are equal compare sums
        {        	    		
    		if (avgNsum[0] > res.getSelectedScore()[0])        
            	return 1;        
            else                  	       
            	return -1;
        }
        
	}
	private Double[] getSelectedScore()//feeds the scores to the comparison method depending on sort criterion set
	{
		int sortBy = this.cycConn.getSortBy();
		switch (sortBy)//Sorts by SUMS first, then AVGS
		{
		case 1:
			this.totalMatchAvg*= -1; //flipping sign so that comparison returns most hits as top result
			this.totalMatchSums*= -1;
			Double[] temp = this.getTotalMatches().clone();
			this.totalMatchAvg*= -1;
			this.totalMatchSums*= -1;
			return temp;			
		case 2:
			return this.getInverseDistances();
		case 3:
			return this.getFirstDistances();						
		case 4:
			return this.getzScores();
		case 5:
			return this.getVectorAngles();
		default:
			return null;
		}
		
	}
			
	//calculates scores according to user preferences
	protected boolean calcRequiredScores()
	{
		if (maps!=null)
		{
			if (!maps.isEmpty())
			{
				this.init();//reset global score variables				
				
				if (cycConn.getRequiredScore()==0)//calc all scores
					return this.calcCategSimScores() && this.calcVectorAngles();
				else if  (cycConn.getRequiredScore()>=1)//calc only same similarity-type scores as selected voter
				{
					if (cycConn.getSortBy()< 5)
						return this.calcCategSimScores();
					else
						return this.calcVectorAngles();
				}				
				else return false;
			}
			else
				return false;
		}
		else if (this.mapsByName!=null)//uses a hashtable of all possible mappings to populate each analogy
		{
			boolean calcSucc = true;
			if (!this.cycConn.scoresCalcd)
				calcSucc = this.cycConn.calcAllPairScores();
			
			if ((calcSucc) && (this.populate()))
			{				
				this.init();	
				if (cycConn.getRequiredScore()==0)//calc all scores
				{
					this.calcCSSumsandAvgs();
					this.calcVSumsandAvg();
				}
				else if  (cycConn.getRequiredScore()>=1)//calc only same similarity-type scores as selected voter
				{
					if (cycConn.getSortBy()< 5)
						this.calcCSSumsandAvgs();
					else
						this.calcVSumsandAvg();
				}
				return true;
			}
			else return false;			
		}
		else return false;
	}
	
	/**
	 * Calculates all scores for all pairs in a given list of pairs.
	 * Also calculates the sums and averages of these scores.
	 * <p>
	 * This converges all the scores for each pair into just one set of scores for the mapping as a whole.
	 * firstDistances are summed and averaged.
	 * inverseDistances are summed and averaged.
	 * totalMatches are summed and averaged.
	 * zScores are calculated using the sample mean method {@link AbstractAnalogyManager#calcFdZScore} & that is divided by n for the averages.
	 * Scores are returned and printed in pairs; sums then averages.
	 *  
	 * @param maps The list of constant pairs from which scores are calculated.
	 * @return true if scores were calculated successfully; false otherwise.
	 */
	private boolean calcCategSimScores() {
				
		for (int i = 0; i < this.maps.size(); i++)
		{	
			if (!maps.get(i).calcCatSimScores()) // get all scores from the ith pair.
				return false;
		}
		
		calcCSSumsandAvgs();
		return true;

	}
	private void calcCSSumsandAvgs() {
		int mapSize = this.maps.size();
		ArrayList<Double> fdList = new ArrayList<Double>();
		for (int i = 0; i < mapSize; i++)	
		{
			double fd = maps.get(i).getSmallestFirstDistance();
			fdList.add(fd);
			this.firstDistanceSums += fd;
			this.inverseDistanceSums += maps.get(i).getAllInverseDistance();
			this.totalMatchSums += maps.get(i).getTotalHits();
		}
		
		if (fdList.size() <= 1)
			this.zScoreSums += this.fDdeviationFromMean(fdList.get(0));
		else
			this.zScoreSums += this.calcFdZScore(fdList);
		
		
		this.firstDistanceAvg = this.firstDistanceSums/mapSize;
		this.inverseDistanceAvg = this.inverseDistanceSums/mapSize;
		this.totalMatchAvg = this.totalMatchSums/mapSize;
		this.zScoreAvg = this.zScoreSums/mapSize;
	}
	
	private boolean calcVectorAngles()//TODO CONTINUE HERE
	{					
		for (int i = 0; i < this.maps.size(); i++)
		{	
			if (!maps.get(i).calcAngle()) // get all scores from the ith pair.
				return false;
		}		
		calcVSumsandAvg();
		return true;					
	}
	private void calcVSumsandAvg() {
		int mapSize = this.maps.size();
		for (int i = 0; i < mapSize; i++)
				this.angleSums+=maps.get(i).getAngle();
		
		this.angleAvg= this.angleSums/mapSize;
	}
	
	/**
	 * 	Helper calculates deviation from mean using:
	 * 	<ul>
	 * 	<li>First distance between two constants.
	 *	<li>An estimated mean (SHORTEST_AVG_LENGTH) aiming to represent avg. path length between all constants of OpenCyc.
	 *	</ul>  
	 *	<p> This is used as a simplified z-score for single-score maps</p>
	 *	
	 *@return (firstDistance - SHORTEST_AVG_LENGTH) / s
	 */
	private double fDdeviationFromMean(double firstDistance)
	{
		double d = firstDistance - AbstractAnalogyManager.SHORTEST_AVG_LENGTH;				
		return d;
	}
	/**
	 * 	Helper calculates multi-variable z-score on a normal curve using:
	 * 	<ul>
	 * 	<li>A list of first distances between constant pairs in a mapping.
	 *	<li>An estimated mean (SHORTEST_AVG_LENGTH) aiming to represent avg. path length between all constants of OpenCyc.
	 *	<li>Standard deviation (s) for multiple-var =
	 *	</ul> 
	 * 	<p> s = SQRT( (1/N) * SumOf((Xi - SHORTEST_AVG_LENGTH)^2) ) 
	 *	
	 *@return (firstDistanceMean - SHORTEST_AVG_LENGTH) / (s / SQRT(n))
	 */
	private double calcFdZScore(ArrayList<Double> fdList)
	{
		double sumOfSquares = 0;
		double sampleMean = 0;
		for (int i = 0; i < fdList.size(); i ++)
		{
			sumOfSquares += Math.pow((fDdeviationFromMean(fdList.get(i))),2);
			sampleMean += fdList.get(i);
		}
		sampleMean = sampleMean / fdList.size();
		double s = Math.sqrt(sumOfSquares / fdList.size());				
		double z = (sampleMean - AbstractAnalogyManager.SHORTEST_AVG_LENGTH)/(s / Math.sqrt(fdList.size()) );
		return z;
	}
	
	


}
